#include "utils/single_app.hpp"
#include <cassert>

#include "utils/path_utils.hpp"

#include "fmt/format.h"
// #include "app/version.h"

#if _WIN32

#include "app/version.h"
#include <Windows.h>
#include <winerror.h>

struct SingleAppGuard::MyData {
    bool   is_self{true};
    HANDLE pid_file{INVALID_HANDLE_VALUE};
    HANDLE app_mutex{INVALID_HANDLE_VALUE};
    DWORD  instance_pid{0};

    void   Init() {
        std::string mutex_name = fmt::format("DiaryApp-{}/{{4219F73C-3379-4CE4-A9A1-F11711DADEE0}}", get_version_string());

        // pid 文件路径
        const std::string pid_file_path = "app.pid"_DP;
        const int         len = MultiByteToWideChar(CP_ACP, 0, pid_file_path.c_str(), -1, nullptr, 0);
        std::wstring      wide_path; wide_path.resize(len + 1); wide_path[len] = 0;
        MultiByteToWideChar(CP_ACP, 0, pid_file_path.c_str(), -1, &wide_path[0], len);

        const HANDLE hMutex = OpenMutexA(MUTEX_ALL_ACCESS, FALSE, mutex_name.data());
        if (hMutex == nullptr)
        {
            // first instance
            app_mutex = CreateMutexA(nullptr, TRUE, mutex_name.data());
            assert (app_mutex);
            is_self = true;
            // 第一个实例，写pid文件
            const HANDLE hFile = CreateFileW(
                wide_path.c_str(),
                GENERIC_WRITE,
                FILE_SHARE_READ,
                nullptr,
                CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
                nullptr
            );
            assert(hFile != INVALID_HANDLE_VALUE);
            const DWORD pid = GetCurrentProcessId();
            DWORD writen = 0;
            const BOOL  r = WriteFile(hFile, &pid, sizeof(pid), &writen, nullptr);
            assert(r);
            pid_file = hFile;
            instance_pid = pid;
        }
        else
        {
            is_self = false;
            app_mutex = hMutex;
            // 说明已经存在实例了，读取pid文件
            const HANDLE hFile = CreateFileW(
                wide_path.c_str(),
                GENERIC_READ,
                FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                nullptr,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                nullptr
            );

            if (hFile != INVALID_HANDLE_VALUE)
            {
                DWORD pid; DWORD read;
                BOOL r = ReadFile(hFile, &pid, sizeof(pid), &read, nullptr);
                assert(r);
                instance_pid = pid;
                pid_file = hFile;
            }
        }
    }
    void   Clean() {
        CloseHandle(app_mutex);
        CloseHandle(pid_file);
    }
    void   Wait() {
        if (is_self)
            return;

        auto hProc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, instance_pid);
        if (hProc) {
            DWORD dwResult = WaitForSingleObject(hProc, 10);
            while (dwResult == WAIT_TIMEOUT) {
                dwResult = WaitForSingleObject(hProc, 10);
            }
            CloseHandle(hProc);
        }
        
        // re-init
        Clean();
        Init();
    }
};

SingleAppGuard::SingleAppGuard (): d{new MyData}
{
    d->Init();
}

SingleAppGuard::~SingleAppGuard ()
{
    d->Clean();
}

SingleAppGuard &SingleAppGuard::Instance ()
{
    static SingleAppGuard inst;
    return inst;
}

bool SingleAppGuard::CheckRunning () const
{
    return !d->is_self;
}

long SingleAppGuard::InstancePID () const
{
    return static_cast<long> (d->instance_pid);
}

void SingleAppGuard::Wait() const
{
    d->Wait();
}

#elif __unix__

#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <cstring>

struct SingleAppGuard::MyData {
    int      pid_file{-1};
    uint32_t pid{0};
    bool     is_owner{false};

    void     Init() {
        const std::string pid_file_path = "app.pid"_DP;
        pid_file = open(pid_file_path.data(), O_CREAT | O_RDWR, 0600);
        if (pid_file < 0) {
            perror(pid_file_path.data());
            abort();
        }

        // lock file
        struct flock lock{};
        memset(&lock, 0x0, sizeof(lock));
        lock.l_type = F_WRLCK;
        int ret = fcntl(pid_file, F_SETLK, &lock);
        if (ret >= 0) {
            // success
            is_owner = true;
            pid = (long)getpid();

            lseek(pid_file, 0, SEEK_SET);
            write(pid_file, &pid, sizeof(pid));
            fsync(pid_file);
        } else {
            // failed
            is_owner = false;

            lseek(pid_file, 0, SEEK_SET);
            read(pid_file, &pid, sizeof(pid));
        }
    }

    void     Clean() {
        const std::string pid_file_path = "app.pid"_DP;
        if (pid_file > 0) {
            if (is_owner) { // 是本程序
                struct flock lock{};
                memset(&lock, 0x0, sizeof(lock));
                lock.l_type = F_UNLCK;
                fcntl(pid_file, F_SETLK, &lock); // 解锁文件

                unlink(pid_file_path.data()); // 设置关闭时删除
            }
            close(pid_file); pid_file = -1; // 关闭文件
        }
    }

    void     Wait() {
        if (is_owner)
            return;
        
        while (kill(pid, 0) == 0) {
            usleep(10*1000); // 10ms
        }

        // re-init
        Clean();
        Init();

        assert (is_owner);
    }
};

SingleAppGuard::SingleAppGuard(): d{new MyData}
{
    d->Init();
}

SingleAppGuard::~SingleAppGuard()
{
    d->Clean();
    delete d;
}

SingleAppGuard& SingleAppGuard::Instance()
{
    static SingleAppGuard inst;
    return inst;
}

bool SingleAppGuard::CheckRunning() const
{
    return !d->is_owner;
}

long SingleAppGuard::InstancePID() const
{
    return static_cast<long>(d->pid);
}

void SingleAppGuard::Wait() const
{
    d->Wait();
}

#else
#error "Code Not Ported Yet!!"
#endif
